/**
* Copyright 2010 CosmoCode GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.cosmocode.palava.maven.ipcstub;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Set;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gag.annotation.remark.OhNoYouDidnt;
import de.cosmocode.commons.reflect.Classpath;
import de.cosmocode.commons.reflect.Reflection;
import de.cosmocode.palava.ipc.IpcCommand;
/* CHECKSTYLE:OFF */
/**
* Generates stub files for all found IpcCommands in the classpath.
*
* @description Generates stub files for all found IpcCommands in the classpath.
* @goal generate-ipcstub
* @requiresDependencyResolution runtime
* @author Tobias Sarnowski
*/
public class GeneratorModule extends AbstractMojo {
/* CHECKSTYLE:ON */
private final Log log = getLog();
/**
* The maven project.
*
* @parameter expression="${project}"
* @required
* @readonly
*/
private MavenProject project;
/**
* List of all generators the use with their configuration.
*
* @parameter
* @required
*/
private List<Generator> generators;
/**
* The generators.
*
* @return the configured generators
*/
public List<Generator> getGenerators() {
return generators;
}
/**
* Generates stub files for all found IpcCommands in the classpath.
*
* {@inheritDoc}
*/
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
final File targetDirectory = new File(project.getBuild().getOutputDirectory(), "ipcstub");
// check configurations and aggregate all required packages
final Set<String> allPackages = Sets.newHashSet();
for (Generator generator : generators) {
generator.check();
allPackages.addAll(generator.getPackages());
}
log.info("Searching for IpcCommands in:");
for (String pkg : allPackages) {
log.info(" " + pkg);
}
// search for IpcCommands in all required packages
final Set<Class<? extends IpcCommand>> foundClasses = Sets.newTreeSet(Reflection.orderByName());
Iterables.addAll(foundClasses, generateCommandList(allPackages));
log.info("Found " + foundClasses.size() + " IpcCommands; generating stubs...");
// filter classes and let the generators do their work
for (Generator generator : generators) {
final Set<Class<? extends IpcCommand>> filteredClasses = Sets.newLinkedHashSet();
for (Class<? extends IpcCommand> foundClass : foundClasses) {
for (String requiredPackage : generator.getPackages()) {
if (foundClass.getName().startsWith(requiredPackage + ".")) {
filteredClasses.add(foundClass);
break;
}
}
}
// whats the target directory?
final File stubTargetDirectory = new File(targetDirectory, generator.getName());
// now call the generator
generator.generate(log, filteredClasses, stubTargetDirectory);
}
}
private Iterable<Class<? extends IpcCommand>> generateCommandList(Set<String> packages)
throws MojoExecutionException {
// create the classpath to use
final List<File> locations = Lists.newArrayList();
try {
for (Object element : project.getRuntimeClasspathElements()) {
log.debug("Adding runtime classpath element: " + element);
locations.add(new File((String) element));
}
} catch (DependencyResolutionRequiredException e) {
throw new MojoExecutionException("dependencies not resolved", e);
}
// hack: add the required files to the classloader
boostrapClassloader(Iterables.transform(locations, new Function<File, URL>() {
@Override
public URL apply(File from) {
try {
return from.toURI().toURL();
} catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
}
}
}));
final String value = Joiner.on(File.pathSeparator).join(locations);
final Classpath cp = Reflection.classpathOf(value);
final Predicate<Class<?>> predicate = Reflection.isConcreteClass();
return cp.restrictTo(packages).filter(IpcCommand.class, predicate);
}
/**
* WARNING: dirtiest maven hack ever!
* @param classpath elements to add to the current classpath
*/
@OhNoYouDidnt
private static void boostrapClassloader(Iterable<URL> classpath) {
final Method method;
try {
final ClassLoader classloader = Thread.currentThread().getContextClassLoader();
final Class<? extends ClassLoader> classloaderClass = classloader.getClass();
method = classloaderClass.getSuperclass().getDeclaredMethod("addURL", URL.class);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("used classloader has no addURL method", e);
}
final boolean accessible = method.isAccessible();
method.setAccessible(true);
try {
for (URL url : classpath) {
method.invoke(Thread.currentThread().getContextClassLoader(), url);
}
} catch (IllegalAccessException e) {
throw new IllegalStateException("cannot call classloader's addURL method", e);
} catch (InvocationTargetException e) {
throw new IllegalStateException("cannot call classloader's addURL method", e);
} finally {
method.setAccessible(accessible);
}
}
}